﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Web.Script.Serialization;
using System.Web;
using System.Data;

namespace Medianamik.Core.Synchronization
{
    public static class SynchronizationHelper
    {
        public static string ToJson<T>(this IEnumerable<T> entities)
            where T : SynchronizedHierarchicalEntity
        {
            var sw = new StringWriter();
            sw.Write(new JavaScriptSerializer().Serialize(entities));
            return sw.ToString();
        }

        public static string ToJson<T>(T entity)
            where T : SynchronizedHierarchicalEntity
        {
            var sw = new StringWriter();
            sw.Write(new JavaScriptSerializer().Serialize(entity));
            return sw.ToString();
        }

        public static IEnumerable<T> FromJson<T>(this string json)
            where T : SynchronizedHierarchicalEntity
        {
            var serializer = new JavaScriptSerializer();
            return serializer.Deserialize<IEnumerable<T>>(json);
        }

        public static IEnumerable<RecordType> ToRecordTypes(this IEnumerable<NodeType> types)
        {
            return new NodeTypeToRecordTypeConverter().ConvertToRecordTypes(types);
        }

        public static IEnumerable<Record> ToRecords(this IEnumerable<Node> nodes)
        {
            return new NodeToRecordConverter().ConvertToRecords(nodes);
        }

        public static void UploadToClient(this HttpResponse response, string data, string fileName)
        {
            response.ContentType = "text/plain";
            response.AppendHeader("Content-Disposition", "attachment; filename=" + fileName + ".txt");
            response.Write(data);
            response.End();
        }

        public static IEnumerable<Record> ToMedianamikRecords(this DataTable table, IEnumerable<PropertyMapping> mappings, ToMedianamikRecordsOptions options)
        {
            if (options == null)
                throw new ArgumentNullException("options");

            return table.AsEnumerable().Select(row => row.ToMedianamikRecord(
                mappings,
                options.TypeName,
                options.DefaultCultureName,
                options.ParentNodeId,
                options.NodeIdColumnName,
                options.ParentNodeIdColumnName,
                options.CreatedOnColumnName,
                options.UpdatedOnColumnName,
                options.CreatedByColumnName,
                options.UpdatedByColumnName,
                options.IgnoreImport,
                options.IsSubEntityColumnName,
                options.ExistsInCurrentCultureColumnName,
                options.PositionColumnName,
                options.IsActiveColumnName,
                options.ParentIdPropertyName,
                options.ParentIdReferencedTypeName,
                options.PreviousIdColumnName,
                options.PreviousIdPropertyName));
        }

        [Obsolete]
        public static IEnumerable<Record> ToMedianamikRecords(this DataTable table, IEnumerable<PropertyMapping> mappings,
            string typeName, string defaultCultureName, Guid? parentId)
        {
            return table.AsEnumerable().Select(row => row.ToMedianamikRecord(mappings, typeName, defaultCultureName,
                 parentId));
        }

        [Obsolete]
        public static IEnumerable<Record> ToMedianamikRecords(this DataTable table, IEnumerable<PropertyMapping> mappings, string typeName,
            string defaultCultureName, Guid? parentId, string idColumnName, string parentIdColumnName, string createdOnColumnName, string updatedOnColumnName, string createdByColumnName,
            string updatedByColumnName, bool ignoreImport, string isSubEntityColumnName, string existsInCurrentCultureColumnName,
            string positionColumnName, string isActiveColumnName, string parentIdPropertyName,
            string parentIdReferencedTypeName, string previousIdColumnName, string previousIdPropertyName)
        {
            return table.AsEnumerable().Select(row => row.ToMedianamikRecord(mappings, typeName,
             defaultCultureName, parentId, idColumnName, parentIdColumnName, createdOnColumnName, updatedOnColumnName, createdByColumnName,
             updatedByColumnName, ignoreImport, isSubEntityColumnName, existsInCurrentCultureColumnName,
             positionColumnName, isActiveColumnName, parentIdPropertyName,
             parentIdReferencedTypeName, previousIdColumnName, previousIdPropertyName));
        }

        //TODO: Remplacer les valeurs null par les valeurs par défaut au plus bas niveau plutôt qu'ici
        public static Record ToMedianamikRecord(this DataRow row, IEnumerable<PropertyMapping> mappings,
            string typeName, string defaultCultureName, Guid? parentId)
        {
            return row.ToMedianamikRecord(mappings, typeName, defaultCultureName,
                 parentId, "nodeid", parentId.HasValue ? null : "parentnodeid", null, null, null,
                null, false, null, null, null, null, null, null, null, null);
        }

        public static Record ToMedianamikRecord(this DataRow row, IEnumerable<PropertyMapping> mappings, string typeName,
            string defaultCultureName, Guid? parentId, string idColumnName, string parentIdColumnName, string createdOnColumnName, string updatedOnColumnName, string createdByColumnName,
            string updatedByColumnName, bool ignoreImport, string isSubEntityColumnName, string existsInCurrentCultureColumnName,
            string positionColumnName, string isActiveColumnName, string parentIdPropertyName,
            string parentIdReferencedTypeName, string previousIdColumnName, string previousIdPropertyName)
        {
            if (parentId.HasValue && !String.IsNullOrEmpty(parentIdColumnName))
                throw new Exception("Do not set parentId if you want to use parentIdColumnName.");

            var record = new Record
            {
                NodeTypeName = typeName,
                DefaultCultureName = defaultCultureName,
                IgnoreImport = ignoreImport,
                ExistsInCurrentCulture = true
            };

            #region Id

            if (idColumnName == null)
            {
                idColumnName = "nodeid";
            }

            record.Id = row.Table.Columns.Contains(idColumnName) && row[idColumnName] != null ? new Guid(row[idColumnName].ToString()) : Guid.NewGuid();

            #endregion

            #region CreatedOn

            if (createdOnColumnName == null)
            {
                createdOnColumnName = "createdon";
            }

            if (!string.IsNullOrEmpty(createdOnColumnName))
            {
                DateTime date;
                if (row.Table.Columns.Contains(createdOnColumnName) &&
                    row[createdOnColumnName] != null &&
                    row[createdOnColumnName] != DBNull.Value &&
                    DateTime.TryParse(row[createdOnColumnName].ToString(), out date))
                {
                    record.CreatedOn = DateTime.Parse(row[createdOnColumnName].ToString());
                }
            }

            #endregion

            #region CreatedBy

            if (createdByColumnName == null)
            {
                createdByColumnName = "createdby";
            }

            if (!string.IsNullOrEmpty(createdByColumnName))
            {
                if (row.Table.Columns.Contains(createdByColumnName) &&
                    row[createdByColumnName] != null &&
                    row[createdByColumnName] != DBNull.Value &&
                    row[createdByColumnName].ToString().Length > 0)
                {
                    record.CreatedBy = row[createdByColumnName].ToString();
                }
            }

            #endregion

            #region ModifiedBy

            if (updatedByColumnName == null)
            {
                updatedByColumnName = "modifiedby";
            }

            if (!string.IsNullOrEmpty(updatedByColumnName))
            {
                if (row.Table.Columns.Contains(updatedByColumnName) &&
                    row[updatedByColumnName] != null &&
                    row[updatedByColumnName] != DBNull.Value &&
                    row[updatedByColumnName].ToString().Length > 0)
                {
                    record.UpdatedBy = row[updatedByColumnName].ToString();
                }
            }

            #endregion

            #region ModifiedOn

            if (updatedOnColumnName == null)
            {
                updatedOnColumnName = "modifiedon";
            }

            if (!string.IsNullOrEmpty(updatedOnColumnName))
            {
                DateTime date;
                if (row.Table.Columns.Contains(updatedOnColumnName) &&
                    row[updatedOnColumnName] != null &&
                    row[updatedOnColumnName] != DBNull.Value &&
                    DateTime.TryParse(row[updatedOnColumnName].ToString(), out date))
                {
                    record.UpdatedOn = DateTime.Parse(row[updatedOnColumnName].ToString());
                }
            }

            #endregion

            #region IsSubEntity

            if (isSubEntityColumnName == null)
            {
                isSubEntityColumnName = "issubentity";
            }

            if (!string.IsNullOrEmpty(isSubEntityColumnName))
            {
                bool isSubEntity;
                if (row.Table.Columns.Contains(isSubEntityColumnName) &&
                    row[isSubEntityColumnName] != null &&
                    row[isSubEntityColumnName] != DBNull.Value &&
                    bool.TryParse(row[isSubEntityColumnName].ToString(), out isSubEntity))
                {
                    record.IsSubEntity = bool.Parse(row[isSubEntityColumnName].ToString());
                }
            }

            #endregion

            #region ExistsInCurrentCulture

            if (existsInCurrentCultureColumnName == null)
            {
                existsInCurrentCultureColumnName = "existsInCurrentCulture";
            }

            if (!string.IsNullOrEmpty(existsInCurrentCultureColumnName))
            {
                bool exists;

                if (row.Table.Columns.Contains(existsInCurrentCultureColumnName) &&
                    row[existsInCurrentCultureColumnName] != null &&
                    row[existsInCurrentCultureColumnName] != DBNull.Value &&
                    bool.TryParse(row[existsInCurrentCultureColumnName].ToString(), out exists))
                {
                    record.ExistsInCurrentCulture = bool.Parse(row[existsInCurrentCultureColumnName].ToString());
                }
            }

            #endregion

            #region Position

            if (positionColumnName == null)
            {
                positionColumnName = "position";
            }

            if (!string.IsNullOrEmpty(positionColumnName))
            {
                short position;
                if (row.Table.Columns.Contains(positionColumnName) &&
                    row[positionColumnName] != null &&
                    row[positionColumnName] != DBNull.Value &&
                    short.TryParse(row[positionColumnName].ToString(), out position))
                {
                    record.Position = short.Parse(row[positionColumnName].ToString());
                }
            }

            #endregion

            #region IsActive

            if (isActiveColumnName == null)
            {
                isActiveColumnName = "isactive";
            }

            if (!string.IsNullOrEmpty(isActiveColumnName))
            {
                bool isActive;
                if (row.Table.Columns.Contains(isActiveColumnName) &&
                    row[isActiveColumnName] != null &&
                    row[isActiveColumnName] != DBNull.Value &&
                    bool.TryParse(row[isActiveColumnName].ToString(), out isActive))
                {
                    record.IsActive = bool.Parse(row[isActiveColumnName].ToString());
                }
            }

            #endregion

            if (!String.IsNullOrEmpty(parentIdPropertyName) &&
                !String.IsNullOrEmpty(parentIdReferencedTypeName))
            {
                record.ParentIdPropertyName = parentIdPropertyName;
                record.ParentIdReferencedTypeName = parentIdReferencedTypeName;
            }

            if (parentId.HasValue)
            {
                record.ParentId = parentId.Value;
            }
            else if (!String.IsNullOrEmpty(parentIdColumnName))
            {
                record.TemporaryParentId = row[parentIdColumnName].ToString();
            }

            if (previousIdPropertyName != null)
            {
                record.IdentifierPropertyName = previousIdPropertyName;
                record.TemporaryId = row[previousIdColumnName].ToString();
            }

            if (!parentId.HasValue && String.IsNullOrEmpty(record.TemporaryParentId) &&
                String.IsNullOrEmpty(record.ParentIdPropertyName) && row.Table.Columns.Contains("ParentNodeId"))
            {
                record.ParentId = (Guid)row["ParentNodeId"];
            }

            mappings.ToMedianamikRecordProperty(row, record).ForEach(p => record.Properties.Add(p));

            return record;
        }

        public static void AddPropertiesToMedianamikRecords(this IList<Record> records, DataTable table,
            IEnumerable<PropertyMapping> mappings, string lookupColumnName)
        {
            foreach (var record in records)
            {
                DataRow[] rows = table.Select(String.Format("{0} = {1}", lookupColumnName,
                    record.TemporaryId));

                if (rows.Count() > 0)
                    mappings.ToMedianamikRecordProperty(rows[0], record)
                        .ForEach(p => record.Properties.Add(p));
            }
        }

        public static void AddPropertiesToMedianamikRecords(this  IList<Record> records,
            DataTable table, IEnumerable<PropertyMapping> mappings, string lookupColumnName,
            string lookupPropertyName)
        {
            foreach (DataRow row in table.Rows)
            {
                string lookupId = row[lookupColumnName].ToString();

                var record = (from r in records
                              from p in r.Properties
                              where p.NodeTypePropertyName.Equals(lookupPropertyName, StringComparison.OrdinalIgnoreCase)
                              && (p.Value == null ? "" : p.Value.ToString()).Equals(lookupId, StringComparison.OrdinalIgnoreCase)
                              select r).FirstOrDefault();

                if (record != null)
                    mappings.ToMedianamikRecordProperty(row, record).ForEach(p => record.Properties.Add(p));
            }
        }

        public static IEnumerable<RecordProperty> ToMedianamikRecordProperty(this IEnumerable<PropertyMapping> mappings,
            DataRow row, Record record)
        {
            foreach (var mapping in mappings)
            {
                var property = new RecordProperty()
                {
                    NodeTypePropertyName = mapping.MedianamikPropertyName,
                    CultureName = mapping.CultureName,
                    ReferencedTypeName = mapping.ReferencedTypeName,
                    ReferencedPropertyName = mapping.ReferencedPropertyName,
                    PropertyType = mapping.PropertyType
                };

                if (mapping.SourcePropertyName == null)
                {
                    //if (mapping.DefaultValue == null)
                    //    throw new Exception("DefaultValue must be provided if SourcePropertyName is null.");

                    object value = mapping.DefaultValue;

                    if (value != null && value.ToString().Contains("@NODEID@"))
                        value = value.ToString().Replace("@NODEID@", record.Id.ToString());

                    property.Value = value;
                }
                else
                {
                    property.Value = null;

                    if (!row.IsNull(mapping.SourcePropertyName) &&
                        row[mapping.SourcePropertyName] != DBNull.Value)
                        property.Value = row[mapping.SourcePropertyName];
                }

                yield return property;
            }
        }
    }
}
